{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "86a4bd36",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "下面展示基于隐马尔科夫模型的序列标注监督学习的代码。这里以命名实体识别任务为例，所使用的数据是Books数据集。为简单起见，标签序列采用BIO格式。首先构建数据集和标签集合："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "41673bbb",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'B-NAME': 1, 'I-ORG': 2, 'B-PRO': 3, 'B-EDU': 4, 'I-NAME': 5, 'B-LOC': 6, 'B-TITLE': 7, 'B-RACE': 8, 'I-TITLE': 9, 'I-RACE': 10, 'I-PRO': 11, 'B-CONT': 12, 'I-EDU': 13, 'I-LOC': 14, 'I-CONT': 15, 'B-ORG': 16, 'O': 0}\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import json\n",
    "from collections import defaultdict\n",
    "\n",
    "\n",
    "class NERDataset:\n",
    "    def __init__(self):\n",
    "        train_file, test_file, label_file = 'ner_train.jsonl',\\\n",
    "            'ner_test.jsonl', 'ner_labels.json'\n",
    "        \n",
    "        def read_file(file_name):\n",
    "            with open(file_name, 'r', encoding='utf-8') as fin:\n",
    "                json_list = list(fin)\n",
    "            data_split = []\n",
    "            for json_str in json_list:\n",
    "                raw = json.loads(json_str)\n",
    "                d = {'tokens': raw['tokens'], 'tags': raw['tags']}\n",
    "                data_split.append(d)\n",
    "            return data_split\n",
    "        \n",
    "        # 读取JSON文件，转化为Python对象\n",
    "        self.train_data, self.test_data = read_file(train_file),\\\n",
    "            read_file(test_file)\n",
    "        self.label2id = json.loads(open(label_file, 'r').read())\n",
    "        self.id2label = {}\n",
    "        for k, v in self.label2id.items():\n",
    "            self.id2label[v] = k\n",
    "        print(self.label2id)\n",
    "\n",
    "    # 建立词表，过滤低频词\n",
    "    def build_vocab(self, min_freq=3):\n",
    "        # 统计词频\n",
    "        frequency = defaultdict(int)\n",
    "        for data in self.train_data:\n",
    "            tokens = data['tokens']\n",
    "            for token in tokens:\n",
    "                frequency[token] += 1\n",
    "                \n",
    "        print(f'unique tokens = {len(frequency)}, '+\\\n",
    "              f'total counts = {sum(frequency.values())}, '+\\\n",
    "              f'max freq = {max(frequency.values())}, '+\\\n",
    "              f'min freq = {min(frequency.values())}')\n",
    "        \n",
    "        # 由于词与标签一一对应，不能随便删除字符，\n",
    "        # 因此加入<unk>用于替代未登录词，加入<pad>用于批量训练\n",
    "        self.token2id = {'<pad>': 0, '<unk>': 1}\n",
    "        self.id2token = {0: '<pad>', 1: '<unk>'}\n",
    "        total_count = 0\n",
    "        # 将高频词加入词表\n",
    "        for token, freq in sorted(frequency.items(),\\\n",
    "                key=lambda x: -x[1]):\n",
    "            if freq > min_freq:\n",
    "                self.token2id[token] = len(self.token2id)\n",
    "                self.id2token[len(self.id2token)] = token\n",
    "                total_count += freq\n",
    "            else:\n",
    "                break\n",
    "        print(f'min_freq = {min_freq}, '\n",
    "              f'remaining tokens = {len(self.token2id)}, '\n",
    "              f'in-vocab rate = {total_count / sum(frequency.values())}')\n",
    "\n",
    "    # 将文本输入转化为词表中对应的索引\n",
    "    def convert_tokens_to_ids(self):\n",
    "        for data_split in [self.train_data, self.test_data]:\n",
    "            for data in data_split:\n",
    "                data['token_ids'] = []\n",
    "                for token in data['tokens']:\n",
    "                    data['token_ids'].append(self.token2id.get(token, 1)) \n",
    "        \n",
    "dataset = NERDataset()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "25650a7c",
   "metadata": {},
   "source": [
    "接下来建立词表，将词元转换为索引，并将数据转化为适合训练的格式。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "a3eb49e5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "unique tokens = 2454, total counts = 182068, max freq = 5630, min freq = 1\n",
      "min_freq = 0, remaining tokens = 2456, in-vocab rate = 1.0\n",
      "[595, 510, 353, 263, 424, 225, 764, 637, 353, 65, 80, 47, 41, 74, 53, 41, 141, 23, 74, 7, 259, 27, 135, 47, 31, 74, 67, 25, 1605, 815, 1157, 225, 595, 1158, 738, 688, 353, 1025, 65, 234, 27, 135, 316, 41, 31, 7, 80, 41, 42, 31, 47, 27, 528, 41, 141, 27, 135, 67, 1005, 1606, 363, 19, 677, 294, 37, 678, 1756, 604, 873, 1006, 1005, 353, 33, 30, 9, 113, 5, 45, 11, 82, 60, 21, 26, 5, 186, 153, 100, 565, 873, 353, 424, 1993, 387, 346, 250, 13, 5, 102, 214, 25, 11, 82, 60, 21, 26, 5, 186, 2, 7, 459, 97, 89, 147, 140, 139, 69, 46, 21, 12, 2, 7, 964, 98, 240, 677, 59, 9, 103, 11, 82, 60, 335, 200, 76, 26, 6, 110, 20, 9, 15, 4, 122, 539, 241, 99, 621, 40, 200, 57, 82, 156, 25, 11, 82, 381, 371, 91, 202, 6, 52, 2, 7, 670, 100, 84, 204, 53, 120, 27, 42, 3, 56, 1159, 3, 329, 31, 265, 31, 3, 56, 394, 394, 3, 84, 357, 84, 7, 25, 397, 7, 41, 7, 74, 7, 135, 7, 31, 7, 87, 7, 259, 7, 31, 7, 74, 7, 41, 7, 131, 7, 28, 289, 385, 4]\n",
      "['阿', '里', '斯', '提', '德', '·', '波', '拉', '斯', '（', 'A', 'r', 'i', 's', 't', 'i', 'd', 'e', 's', ' ', 'B', 'o', 'u', 'r', 'a', 's', '）', '和', '卢', '卡', '雅', '·', '阿', '伊', '纳', '罗', '斯', '托', '（', 'L', 'o', 'u', 'k', 'i', 'a', ' ', 'A', 'i', 'n', 'a', 'r', 'o', 'z', 'i', 'd', 'o', 'u', '）', '夫', '妇', '二', '人', '均', '拥', '有', '希', '腊', '比', '雷', '埃', '夫', '斯', '技', '术', '教', '育', '学', '院', '计', '算', '机', '工', '程', '学', '位', '以', '及', '色', '雷', '斯', '德', '谟', '克', '利', '特', '大', '学', '电', '子', '和', '计', '算', '机', '工', '程', '学', '位', '，', ' ', '都', '从', '事', '过', '软', '件', '开', '发', '工', '作', '，', ' ', '且', '目', '前', '均', '为', '教', '授', '计', '算', '机', '相', '关', '课', '程', '的', '高', '中', '教', '师', '。', '他', '们', '写', '了', '很', '多', '关', '于', '算', '法', '和', '计', '算', '思', '维', '方', '面', '的', '书', '，', ' ', '涉', '及', 'P', 'y', 't', 'h', 'o', 'n', '、', 'C', '#', '、', 'J', 'a', 'v', 'a', '、', 'C', '+', '+', '、', 'P', 'H', 'P', ' ', '和', 'V', ' ', 'i', ' ', 's', ' ', 'u', ' ', 'a', ' ', 'l', ' ', 'B', ' ', 'a', ' ', 's', ' ', 'i', ' ', 'c', ' ', '等', '语', '言', '。']\n"
     ]
    }
   ],
   "source": [
    "# 截取一部分数据便于更快训练\n",
    "dataset.train_data = dataset.train_data[:1000]\n",
    "dataset.test_data = dataset.test_data[:200]\n",
    "\n",
    "dataset.build_vocab(min_freq=0)\n",
    "dataset.convert_tokens_to_ids()\n",
    "print(dataset.train_data[0]['token_ids'])\n",
    "print([dataset.id2token[token_id] for token_id in \\\n",
    "       dataset.train_data[0]['token_ids']])\n",
    "\n",
    "def collect_data(data_split):\n",
    "    X, Y = [], []\n",
    "    for data in data_split:\n",
    "        assert len(data['token_ids']) == len(data['tags'])\n",
    "        X.append(data['token_ids'])\n",
    "        Y.append(data['tags'])\n",
    "    return X, Y\n",
    "\n",
    "train_X, train_Y = collect_data(dataset.train_data)\n",
    "test_X, test_Y = collect_data(dataset.test_data)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7de17192",
   "metadata": {},
   "source": [
    "随后实现隐马尔科夫模型，使用最大似然估计得到模型参数，使用维特比算法进行解码。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "3229fd6a",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "class HMM:\n",
    "    def __init__(self, n_tags, n_tokens):\n",
    "        self.n_tags = n_tags\n",
    "        self.n_tokens = n_tokens\n",
    "    \n",
    "    # 使用最大似然估计计算模型参数\n",
    "    def fit(self, X, Y):\n",
    "        Y0_cnt = np.zeros(self.n_tags)\n",
    "        YY_cnt = np.zeros((self.n_tags, self.n_tags))\n",
    "        YX_cnt = np.zeros((self.n_tags, self.n_tokens))\n",
    "        for x, y in zip(X, Y):\n",
    "            Y0_cnt[y[0]] += 1\n",
    "            last_y = y[0]\n",
    "            for i in range(1, len(y)):\n",
    "                YY_cnt[last_y, y[i]] += 1\n",
    "                last_y = y[i]\n",
    "            for xi, yi in zip(x, y):\n",
    "                YX_cnt[yi, xi] += 1\n",
    "        self.init_prob = Y0_cnt / Y0_cnt.sum()\n",
    "        self.transition_prob = YY_cnt\n",
    "        self.emission_prob = YX_cnt\n",
    "        for i in range(self.n_tags):\n",
    "            # 为了避免训练集过小时除0\n",
    "            yy_sum = YY_cnt[i].sum()\n",
    "            if yy_sum > 0:\n",
    "                self.transition_prob[i] = YY_cnt[i] / yy_sum\n",
    "            yx_sum = YX_cnt[i].sum()\n",
    "            if yx_sum > 0:\n",
    "                self.emission_prob[i] = YX_cnt[i] / yx_sum\n",
    "    \n",
    "    # 已知模型参数的条件下，使用维特比算法解码得到最优标签序列\n",
    "    def viterbi(self, x):\n",
    "        assert hasattr(self, 'init_prob') and hasattr(self,\\\n",
    "            'transition_prob') and hasattr(self, 'emission_prob')\n",
    "        Pi = np.zeros((len(x), self.n_tags))\n",
    "        Y = np.zeros((len(x), self.n_tags), dtype=np.int32)\n",
    "        # 初始化\n",
    "        for i in range(self.n_tags):\n",
    "            Pi[0, i] = self.init_prob[i] * self.emission_prob[i, x[0]]\n",
    "            Y[0, i] = -1\n",
    "        for t in range(1, len(x)):\n",
    "            for i in range(self.n_tags):\n",
    "                tmp = []\n",
    "                for j in range(self.n_tags):\n",
    "                    tmp.append(self.transition_prob[j, i] * Pi[t-1, j])\n",
    "                best_j = np.argmax(tmp)\n",
    "                # 维特比算法递推公式\n",
    "                Pi[t, i] = self.emission_prob[i, x[t]] * tmp[best_j]\n",
    "                Y[t, i] = best_j\n",
    "        y = [np.argmax(Pi[-1])]\n",
    "        for t in range(len(x)-1, 0, -1):\n",
    "            y.append(Y[t, y[-1]])\n",
    "        return np.max(Pi[len(x)-1]), y[::-1]\n",
    "    \n",
    "    def decode(self, X):\n",
    "        Y = []\n",
    "        for x in X:\n",
    "            _, y = self.viterbi(x)\n",
    "            Y.append(y)\n",
    "        return Y\n",
    "\n",
    "hmm = HMM(len(dataset.label2id), len(dataset.token2id))\n",
    "hmm.fit(train_X, train_Y)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "32868ed6",
   "metadata": {},
   "source": [
    "最后验证模型效果"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "4f4b7e88",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "precision = 0.45762149610217284, recall = 0.3289222699093944, f1 = 0.38274259554692375\n",
      "precision = 0.4189636163175303, recall = 0.23270055113288426, f1 = 0.2992125984251969\n"
     ]
    }
   ],
   "source": [
    "def extract_entity(labels):\n",
    "    entity_list = []\n",
    "    entity_start = -1\n",
    "    entity_length = 0\n",
    "    entity_type = None\n",
    "    for token_index, label in enumerate(labels):\n",
    "        if label.startswith('B'):\n",
    "            if entity_start != -1:\n",
    "                # 遇到了一个新的B，将上一个实体加入列表\n",
    "                entity_list.append((entity_start, entity_length,\\\n",
    "                    entity_type))\n",
    "            # 记录新实体\n",
    "            entity_start = token_index\n",
    "            entity_length = 1\n",
    "            entity_type = label.split('-')[1]\n",
    "        elif label.startswith('I'):\n",
    "            if entity_start != -1:\n",
    "                # 上一个实体未关闭，遇到了一个新的I\n",
    "                if entity_type == label.split('-')[1]:\n",
    "                    # 若上一个实体与当前类型相同，长度+1\n",
    "                    entity_length += 1\n",
    "                else:\n",
    "                    # 若上一个实体与当前类型不同，\n",
    "                    # 将上一个实体加入列表，重置实体\n",
    "                    entity_list.append((entity_start, entity_length,\\\n",
    "                        entity_type))\n",
    "                    entity_start = -1\n",
    "                    entity_length = 0\n",
    "                    entity_type = None\n",
    "        else:\n",
    "            if entity_start != -1:\n",
    "                # 遇到了一个新的O，将上一个实体加入列表\n",
    "                entity_list.append((entity_start, entity_length,\\\n",
    "                    entity_type))\n",
    "            # 重置实体\n",
    "            entity_start = -1\n",
    "            entity_length = 0\n",
    "            entity_type = None\n",
    "    if entity_start != -1:\n",
    "        # 将上一个实体加入列表\n",
    "        entity_list.append((entity_start, entity_length, entity_type))\n",
    "    return entity_list\n",
    "\n",
    "def compute_metric(Y, P):\n",
    "    true_entity_set = set()\n",
    "    pred_entity_set = set()\n",
    "    for sent_no, labels in enumerate(Y):\n",
    "        labels = [dataset.id2label[x] for x in labels]\n",
    "        for ent in extract_entity(labels):\n",
    "            true_entity_set.add((sent_no, ent))\n",
    "    for sent_no, labels in enumerate(P):\n",
    "        labels = [dataset.id2label[x] for x in labels]\n",
    "        for ent in extract_entity(labels):\n",
    "            pred_entity_set.add((sent_no, ent))\n",
    "    if len(true_entity_set) > 0:\n",
    "        recall = len(true_entity_set & pred_entity_set)\\\n",
    "            / len(true_entity_set)\n",
    "    else:\n",
    "        recall = 0\n",
    "    if len(pred_entity_set) > 0:\n",
    "        precision = len(true_entity_set & pred_entity_set)\\\n",
    "            / len(pred_entity_set)\n",
    "    else:\n",
    "        precision = 0\n",
    "    if precision > 0 and recall > 0:\n",
    "        f1 = 2 * precision * recall / (precision + recall)\n",
    "    else:\n",
    "        f1 = 0\n",
    "    return precision, recall, f1\n",
    "\n",
    "train_P = hmm.decode(train_X)\n",
    "p, r, f = compute_metric(train_Y, train_P)\n",
    "print(f'precision = {p}, recall = {r}, f1 = {f}')\n",
    "test_P = hmm.decode(test_X)\n",
    "p, r, f = compute_metric(test_Y, test_P)\n",
    "print(f'precision = {p}, recall = {r}, f1 = {f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3abe8397",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "下面展示基于条件随机场的序列标注模型。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "787f8ad1",
   "metadata": {},
   "outputs": [],
   "source": [
    "\"\"\"\n",
    "代码修改自GitHub项目kmkurn/pytorch-crf\n",
    "（Copyright (c) 2019, Kemal Kurniawan, MIT License（见附录））\n",
    "\"\"\"\n",
    "import torch\n",
    "from torch import nn\n",
    "\n",
    "class CRFLayer(nn.Module):\n",
    "    def __init__(self, n_tags, n_features):\n",
    "        super().__init__()\n",
    "        self.n_tags = n_tags\n",
    "        self.n_features = n_features\n",
    "        # 定义模型参数\n",
    "        self.transitions = nn.Parameter(torch.empty(\\\n",
    "            n_tags, n_tags))\n",
    "        self.emission_weight = nn.Parameter(torch.empty(\\\n",
    "            n_features, n_tags))\n",
    "        self.start_transitions = nn.Parameter(torch.empty(n_tags))\n",
    "        self.end_transitions = nn.Parameter(torch.empty(n_tags))\n",
    "        self.reset_parameters()\n",
    "        \n",
    "    # 使用（-0.1,0.1）之间的均匀分布初始化参数\n",
    "    def reset_parameters(self):\n",
    "        nn.init.uniform_(self.transitions, -0.1, 0.1)\n",
    "        nn.init.uniform_(self.emission_weight, -0.1, 0.1)\n",
    "        nn.init.uniform_(self.start_transitions, -0.1, 0.1)\n",
    "        nn.init.uniform_(self.end_transitions, -0.1, 0.1)\n",
    "    \n",
    "    # 使用动态规划计算得分\n",
    "    def compute_score(self, emissions, tags, masks):\n",
    "        seq_len, batch_size, n_tags = emissions.shape\n",
    "        score = self.start_transitions[tags[0]] + \\\n",
    "            emissions[0, torch.arange(batch_size), tags[0]]\n",
    "        \n",
    "        for i in range(1, seq_len):\n",
    "            score += self.transitions[tags[i-1], tags[i]] * masks[i]\n",
    "            score += emissions[i, torch.arange(batch_size),\\\n",
    "                tags[i]] * masks[i]\n",
    "        \n",
    "        seq_ends = masks.long().sum(dim=0) - 1\n",
    "        last_tags = tags[seq_ends, torch.arange(batch_size)]\n",
    "        score += self.end_transitions[last_tags]\n",
    "        return score\n",
    "    \n",
    "    # 计算配分函数\n",
    "    def computer_normalizer(self, emissions, masks):\n",
    "        seq_len, batch_size, n_tags = emissions.shape\n",
    "        # batch_size * n_tags, [起始分数 + y_0为某标签的发射分数 ...]\n",
    "        score = self.start_transitions + emissions[0]\n",
    "        \n",
    "        for i in range(1, seq_len):\n",
    "            # batch_size * n_tags * 1 [y_{i-1}为某tag的总分]\n",
    "            broadcast_score = score.unsqueeze(2)\n",
    "            # batch_size * 1 * n_tags [y_i为某标签的发射分数]\n",
    "            broadcast_emissions = emissions[i].unsqueeze(1)\n",
    "            # batch_size * n_tags * n_tags [任意y_{i-1}到y_i的总分]\n",
    "            next_score = broadcast_score + self.transitions +\\\n",
    "                broadcast_emissions\n",
    "            # batch_size * n_tags [对y_{i-1}求和]\n",
    "            next_score = torch.logsumexp(next_score, dim=1)\n",
    "            # masks为True则更新，否则保留\n",
    "            score = torch.where(masks[i].unsqueeze(1), next_score, score)\n",
    "            \n",
    "        score += self.end_transitions\n",
    "        return torch.logsumexp(score, dim=1)\n",
    "    \n",
    "    def forward(self, features, tags, masks):\n",
    "        \"\"\"\n",
    "        features: seq_len * batch_size * n_features\n",
    "        tags/masks: seq_len * batch_size\n",
    "        \"\"\"\n",
    "        _, batch_size, _ = features.size()\n",
    "        emissions = torch.matmul(features, self.emission_weight)\n",
    "        masks = masks.to(torch.bool)\n",
    "        \n",
    "        score = self.compute_score(emissions, tags, masks)\n",
    "        partition = self.computer_normalizer(emissions, masks)\n",
    "        \n",
    "        likelihood = score - partition\n",
    "        return likelihood.sum() / batch_size\n",
    "    \n",
    "    def decode(self, features, masks):\n",
    "        # 与computer_normalizer类似，sum变为max\n",
    "        emissions = torch.matmul(features, self.emission_weight)\n",
    "        masks = masks.to(torch.bool)\n",
    "        \n",
    "        seq_len, batch_size, n_tags = emissions.shape\n",
    "        score = self.start_transitions + emissions[0]\n",
    "        history = []\n",
    "        \n",
    "        for i in range(1, seq_len):\n",
    "            broadcast_score = score.unsqueeze(2)\n",
    "            broadcast_emission = emissions[i].unsqueeze(1)\n",
    "            \n",
    "            next_score = broadcast_score + self.transitions +\\\n",
    "                broadcast_emission\n",
    "            next_score, indices = next_score.max(dim=1)\n",
    "            \n",
    "            score = torch.where(masks[i].unsqueeze(1), next_score,\\\n",
    "                score)\n",
    "            history.append(indices)\n",
    "            \n",
    "        score += self.end_transitions\n",
    "        seq_ends = masks.long().sum(dim=0) - 1\n",
    "        best_tags_list = []\n",
    "        \n",
    "        for idx in range(batch_size):\n",
    "            _, best_last_tag = score[idx].max(dim=0)\n",
    "            best_tags = [best_last_tag.item()]\n",
    "            \n",
    "            for hist in reversed(history[:seq_ends[idx]]):\n",
    "                best_last_tag = hist[idx][best_tags[-1]]\n",
    "                best_tags.append(best_last_tag.item())\n",
    "                \n",
    "            best_tags.reverse()\n",
    "            best_tags_list.append(best_tags)\n",
    "            \n",
    "        return best_tags_list"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "69f4af96",
   "metadata": {},
   "outputs": [],
   "source": [
    "class CRF(nn.Module):\n",
    "    def __init__(self, vocab_size, hidden_size, n_tags):\n",
    "        super().__init__()\n",
    "        self.embedding = nn.Embedding(vocab_size, hidden_size)\n",
    "        self.crf = CRFLayer(n_tags, hidden_size)\n",
    "        \n",
    "    def forward(self, input_ids, masks, labels):\n",
    "        \"\"\"\n",
    "        input_ids/masks/labels: batch_size * seq_len\n",
    "        \"\"\"\n",
    "        # 将输入序列转化为词嵌入序列\n",
    "        # batch_size * seq_len * embed_size\n",
    "        embed = self.embedding(input_ids)\n",
    "        embed = torch.transpose(embed, 0, 1)\n",
    "        masks = torch.transpose(masks, 0, 1)\n",
    "        labels = torch.transpose(labels, 0, 1)\n",
    "        # 隐状态和标签、掩码输入条件随机场计算损失\n",
    "        llh = self.crf(embed, labels, masks)\n",
    "        return -llh\n",
    "    \n",
    "    def decode(self, input_ids, masks):\n",
    "        embed = self.embedding(input_ids)\n",
    "        embed = torch.transpose(embed, 0, 1)\n",
    "        masks = torch.transpose(masks, 0, 1)\n",
    "        # 调用CRFLayer进行解码\n",
    "        return self.crf.decode(embed, masks)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "178948b4",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "epoch-19, loss=44.78: 100%|█| 20/20 [02:05<00:00,  6.29s/it]\n"
     ]
    },
    {
     "data": {
      "image/png": "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IpHg9ddfv21PT3OZNm0aUlJS0LFjR3Tp0gVLly5FcXHxbfcxSk5OxqhRo9CpUycUFxfjl19+QUxMDACgXbt2kEgk+P777zF69Gh4e3vD398fs2bNwksvvQSr1YqBAweipKQEe/bsgZ+fHyZNmiS+95tvvgmNRgOtVotXX30VISEh4oq3230uETUNe4iIyG5mzZoFmUyGrl27olWrVredD7R48WIEBQWhf//+GDduHEaMGIHevXu3YG2rzZkzB48//jgmTpyIfv36wc/PDyNGjICXl9ctX2OxWPDCCy8gJiYGI0eOROfOnfHhhx8CANq0aYM33ngDr7zyCrRaLaZOnQoA+J//+R/8/e9/R0pKCmJiYjBixAh89913iIqKsnnvt99+Gy+++CLi4+ORn5+PjRs32vQ63epziahpuFM1EdENrFYrYmJiMH78ePzP//xPi30ud7gmciwOmRGRRzt//jy2bNmCQYMGwWAwYNmyZcjOzsaECRMcXTUiakEcMiMijyaVSrFq1Sr06dMHAwYMwNGjR7Ft2zbOzSHyMBwyIyIiIo/HHiIiIiLyeAxERERE5PEYiIiIiMjjMRARERGRx2MgIiIiIo/HQEREREQej4GIiIiIPB4DEREREXm8/w9ib/CeMlPFUQAAAABJRU5ErkJggg==",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import numpy as np\n",
    "from torch.utils.data import Dataset, DataLoader\n",
    "from torch.optim import SGD, Adam\n",
    "from copy import deepcopy\n",
    "\n",
    "# 将数据适配为PyTorch数据集\n",
    "class SeqLabelDataset(Dataset):\n",
    "    def __init__(self, tokens, labels):\n",
    "        self.tokens = deepcopy(tokens)\n",
    "        self.labels = deepcopy(labels)\n",
    "        \n",
    "    def __getitem__(self, idx):\n",
    "        return self.tokens[idx], self.labels[idx]\n",
    "    \n",
    "    def __len__(self):\n",
    "        return len(self.labels)\n",
    "    \n",
    "    # 将每个批次转化为PyTorch张量\n",
    "    @classmethod\n",
    "    def collate_batch(cls, inputs):\n",
    "        input_ids, labels, masks = [], [], []\n",
    "        max_len = -1\n",
    "        for ids, tags in inputs:\n",
    "            input_ids.append(ids)\n",
    "            labels.append(tags)\n",
    "            masks.append([1] * len(tags))\n",
    "            max_len = max(max_len, len(ids))\n",
    "        for ids, tags, msks in zip(input_ids, labels, masks):\n",
    "            pad_len = max_len - len(ids)\n",
    "            ids.extend([0] * pad_len)\n",
    "            tags.extend([0] * pad_len)\n",
    "            msks.extend([0] * pad_len)\n",
    "        input_ids = torch.tensor(np.array(input_ids),\\\n",
    "            dtype=torch.long)\n",
    "        labels = torch.tensor(np.array(labels), dtype=torch.long)\n",
    "        masks = torch.tensor(np.array(masks), dtype=torch.uint8)\n",
    "        return {'input_ids': input_ids, 'masks': masks,\\\n",
    "                'labels': labels}\n",
    "    \n",
    "# 准备数据\n",
    "train_dataset = SeqLabelDataset(train_X, train_Y)\n",
    "test_dataset = SeqLabelDataset(test_X, test_Y)\n",
    "\n",
    "from tqdm import tqdm, trange\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "def train(model, batch_size, epochs, learning_rate):\n",
    "    train_dataloader = DataLoader(train_dataset,\n",
    "        batch_size=batch_size, shuffle=True, \n",
    "        collate_fn=SeqLabelDataset.collate_batch)\n",
    "    optimizer = Adam(model.parameters(), lr=learning_rate)\n",
    "    model.zero_grad()\n",
    "    tr_step = []\n",
    "    tr_loss = []\n",
    "    global_step = 0\n",
    "    \n",
    "    with trange(epochs, desc='epoch', ncols=60) as pbar:\n",
    "        for epoch in pbar:\n",
    "            model.train()\n",
    "            for step, batch in enumerate(train_dataloader):\n",
    "                global_step += 1\n",
    "                loss = model(**batch)\n",
    "                loss.backward()\n",
    "                optimizer.step()\n",
    "                model.zero_grad()\n",
    "                pbar.set_description(f'epoch-{epoch}, '+\\\n",
    "                    f'loss={loss.item():.2f}')\n",
    "                \n",
    "                if epoch > 0:\n",
    "                    tr_step.append(global_step)\n",
    "                    tr_loss.append(loss.item())\n",
    "\n",
    "    # 打印损失曲线\n",
    "    plt.plot(tr_step, tr_loss, label='train loss')\n",
    "    plt.xlabel('training steps')\n",
    "    plt.ylabel('loss')\n",
    "    plt.legend()\n",
    "    plt.show()\n",
    "                \n",
    "vocab_size = len(dataset.token2id)\n",
    "hidden_size = 32\n",
    "n_tags = len(dataset.label2id)\n",
    "crf = CRF(vocab_size, hidden_size, n_tags)\n",
    "batch_size = 128\n",
    "epochs = 20\n",
    "learning_rate = 1e-2\n",
    "train(crf, batch_size, epochs, learning_rate)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "4a1e7e25",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "precision = 0.5462592804111936, recall = 0.22806390081068192, f1 = 0.3217830109335576\n",
      "precision = 0.45726495726495725, recall = 0.19657072872014697, f1 = 0.2749464668094218\n"
     ]
    }
   ],
   "source": [
    "# 验证效果\n",
    "\n",
    "def evaluate(X, Y, model, batch_size):\n",
    "    dataset = SeqLabelDataset(X, Y)\n",
    "    dataloader = DataLoader(dataset, batch_size=batch_size,\\\n",
    "        collate_fn=SeqLabelDataset.collate_batch)\n",
    "    model.eval()\n",
    "    with torch.no_grad():\n",
    "        P = []\n",
    "        for batch in dataloader:\n",
    "            preds = model.decode(batch['input_ids'], batch['masks'])\n",
    "            P.extend(preds)\n",
    "\n",
    "    p, r, f = compute_metric(Y, P)\n",
    "    print(f'precision = {p}, recall = {r}, f1 = {f}')\n",
    "    \n",
    "evaluate(train_X, train_Y, crf, batch_size)\n",
    "evaluate(test_X, test_Y, crf, batch_size)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cc369a17",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "这里介绍基于双向长短期记忆-条件随机场（BiLSTM-CRF）结构的代码实现。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "990c5bc1",
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "from torch import nn\n",
    "\n",
    "# 定义模型，将长短期记忆的输出输入给条件随机场，\n",
    "# 利用条件随机场计算损失和解码\n",
    "class LSTM_CRF(nn.Module):\n",
    "    def __init__(self, vocab_size, hidden_size, num_layers,\\\n",
    "                 dropout, n_tags):\n",
    "        super().__init__()\n",
    "        self.embedding = nn.Embedding(vocab_size, hidden_size)\n",
    "        self.lstm = nn.LSTM(input_size=hidden_size,\n",
    "                            hidden_size=hidden_size,\n",
    "                            num_layers=num_layers,\n",
    "                            batch_first=False,\n",
    "                            dropout=dropout,\n",
    "                            bidirectional=True)\n",
    "        self.crf = CRFLayer(n_tags, hidden_size * 2)\n",
    "    \n",
    "    def forward(self, input_ids, masks, labels):\n",
    "        \"\"\"\n",
    "        input_ids/masks/labels: batch_size * seq_len\n",
    "        \"\"\"\n",
    "        # 将输入序列转化为词嵌入序列\n",
    "        # batch_size * seq_len * embed_size\n",
    "        embed = self.embedding(input_ids)\n",
    "        embed = torch.transpose(embed, 0, 1)\n",
    "        masks = torch.transpose(masks, 0, 1)\n",
    "        labels = torch.transpose(labels, 0, 1)\n",
    "        # 输入长短期记忆得到隐状态\n",
    "        hidden_states, _ = self.lstm(embed)\n",
    "        # 隐状态和标签、掩码输入条件随机场计算损失\n",
    "        llh = self.crf(hidden_states, labels, masks)\n",
    "        return -llh\n",
    "    \n",
    "    def decode(self, input_ids, masks):\n",
    "        # 输入长短期记忆得到隐状态\n",
    "        embed = self.embedding(input_ids)\n",
    "        embed = torch.transpose(embed, 0, 1)\n",
    "        masks = torch.transpose(masks, 0, 1)\n",
    "        hidden_states, _ = self.lstm(embed)\n",
    "        # 调用条件随机场进行解码\n",
    "        return self.crf.decode(hidden_states, masks)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "ab41a9f0",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "epoch-19, loss=32.41: 100%|█| 20/20 [02:48<00:00,  8.40s/it]\n"
     ]
    },
    {
     "data": {
      "image/png": "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ww8cOnQIgJ07d7J27VomTpwIuF5/HarTr61bt1JUVOR0T2xsLL17927RfXfIyMjAMAxr1NLV+muz2bjnnnt46qmn6NWrV4XrrtRfm83GN998Q9euXRk/fjyRkZEMGTLEaaqrMfursONiTNPkiSeeYOTIkfTu3RuAlJQUAKKiopzujYqKsq61NAsWLGDbtm3Mnj27wjVX6++xY8d466236NKlC4sXL+bBBx/k0Ucf5cMPPwRcr7/PPPMMd9xxB927d8fT05MBAwYwY8YM7rjjDsD1+utQnX6lpKTg5eVFaGholfe0VPn5+fzmN7/hzjvvtA6LdLX+/ulPf8LDw4NHH3200uuu1N/U1FSys7N55ZVXmDBhAkuWLOGmm27i5ptvZtWqVUDj9lennruYhx9+mF27drF27doK1wzDcHptmmaF91qCxMREHnvsMZYsWeI093shV+mvzWZj0KBBvPzyywAMGDCAvXv38tZbb3Hvvfda97lKfz/55BM++ugjPv74Y3r16sWOHTuYMWMGsbGxTJ061brPVfp7odr0q6X3vaioiJ/97GfYbDb+/ve/X/L+ltjfrVu38uabb7Jt27Yat70l9texoGDKlCk8/vjjAPTv359169bxj3/8g1GjRlX52Ybor0Z2XMgjjzzCl19+yYoVK2jTpo31fnR0NECFpJyamlrhvyJbgq1bt5KamsrAgQPx8PDAw8ODVatW8de//hUPDw+rT67S35iYGHr27On0Xo8ePawVDa727/epp57iN7/5DT/72c/o06cP99xzD48//rg1iudq/XWoTr+io6MpLCwkLS2tyntamqKiIm677Tbi4+NZunSpNaoDrtXfNWvWkJqaStu2ba0/t06cOMGTTz5J+/btAdfqb0REBB4eHpf8s6ux+quw4wJM0+Thhx/m888/Z/ny5XTo0MHpeocOHYiOjmbp0qXWe4WFhaxatYrhw4c3dnPr7Nprr2X37t3s2LHD+hk0aBB33XUXO3bsoGPHji7V3xEjRlTYSuDQoUO0a9cOcL1/v7m5ubi5Of/R5O7ubv2Xoqv116E6/Ro4cCCenp5O9yQnJ7Nnz54W2XdH0Dl8+DDLli0jPDzc6bor9feee+5h165dTn9uxcbG8tRTT7F48WLAtfrr5eXF4MGDL/pnV6P2t17LnaVJ/PrXvzaDg4PNlStXmsnJydZPbm6udc8rr7xiBgcHm59//rm5e/du84477jBjYmLMzMzMJmx5/Sm/Gss0Xau/mzZtMj08PMyXXnrJPHz4sPmf//zH9PPzMz/66CPrHlfq79SpU83WrVubX3/9tRkfH29+/vnnZkREhPn0009b97TU/mZlZZnbt283t2/fbgLm66+/bm7fvt1afVSdfj344INmmzZtzGXLlpnbtm0zr7nmGrNfv35mcXFxU3WrShfrb1FRkTl58mSzTZs25o4dO5z+7CooKLCe4Sr9rcyFq7FM07X6+/nnn5uenp7m22+/bR4+fNicO3eu6e7ubq5Zs8Z6RmP1V2HHBQCV/rz33nvWPTabzfz9739vRkdHm97e3uZVV11l7t69u+kaXc8uDDuu1t+vvvrK7N27t+nt7W12797dfPvtt52uu1J/MzMzzccee8xs27at6ePjY3bs2NF87rnnnP4CbKn9XbFiRaX/W506dappmtXrV15envnwww+bYWFhpq+vr3nDDTeYCQkJTdCbS7tYf+Pj46v8s2vFihXWM1ylv5WpLOy4Wn/fffdds3PnzqaPj4/Zr18/c9GiRU7PaKz+GqZpmvU7ViQiIiLSfKhmR0RERFyawo6IiIi4NIUdERERcWkKOyIiIuLSFHZERETEpSnsiIiIiEtT2BERERGXprAjIiIiLk1hR0SaRPv27XnjjTeqff/KlSsxDIP09PQGa5OIuCbtoCwi1TJ69Gj69+9fo4ByMWfOnMHf3x8/P79q3V9YWMj58+eJiorCMIx6aUNNrVy5kquvvpq0tDRCQkKapA0iUnMeTd0AEXEdpmlSUlKCh8el/2hp1apVjZ7t5eVFdHR0bZsmIpcxTWOJyCVNmzaNVatW8eabb2IYBoZhcPz4cWtqafHixQwaNAhvb2/WrFnD0aNHmTJlClFRUQQEBDB48GCWLVvm9MwLp7EMw+Bf//oXN910E35+fnTp0oUvv/zSun7hNNb7779PSEgIixcvpkePHgQEBDBhwgSSk5OtzxQXF/Poo48SEhJCeHg4zzzzDFOnTuXGG2+ssq8nTpxg0qRJhIaG4u/vT69evfj22285fvw4V199NQChoaEYhsG0adMAe8h79dVX6dixI76+vvTr149PP/20Qtu/+eYb+vXrh4+PD0OGDGH37t2X/F4RqTuFHRG5pDfffJNhw4bxy1/+kuTkZJKTk4mLi7OuP/3008yePZv9+/fTt29fsrOzmThxIsuWLWP79u2MHz+eSZMmkZCQcNHveeGFF7jtttvYtWsXEydO5K677uL8+fNV3p+bm8uf//xn5s+fz+rVq0lISGDmzJnW9T/96U/85z//4b333uPHH38kMzOTRYsWXbQN06dPp6CggNWrV7N7927+9Kc/ERAQQFxcHJ999hkABw8eJDk5mTfffBOA3/72t7z33nu89dZb7N27l8cff5y7776bVatWOT37qaee4s9//jObN28mMjKSyZMnU1RUdNHvFZF6UO/nqIuISxo1apT52GOPOb23YsUKEzAXLVp0yc/37NnTnDt3rvW6Xbt25pw5c6zXgPnb3/7Wep2dnW0ahmF+9913Tt+VlpZmmqZpvvfeeyZgHjlyxPrM3/72NzMqKsp6HRUVZb722mvW6+LiYrNt27bmlClTqmxnnz59zFmzZlV67cI2ONrp4+Njrlu3zune++67z7zjjjucPrdgwQLr+rlz50xfX1/zk08+ueT3ikjdqGZHROps0KBBTq9zcnJ44YUX+Prrrzl16hTFxcXk5eVdcmSnb9++1u/+/v4EBgaSmppa5f1+fn506tTJeh0TE2Pdn5GRwenTp7nyyiut6+7u7gwcOBCbzVblMx999FF+/etfs2TJEsaMGcMtt9zi1K4L7du3j/z8fMaOHev0fmFhIQMGDHB6b9iwYdbvYWFhdOvWjf3799fqe0Wk+jSNJSJ15u/v7/T6qaee4rPPPuOll15izZo17Nixgz59+lBYWHjR53h6ejq9NgzjosGksvvNCxaYXrhy68LrF7r//vs5duwY99xzD7t372bQoEHMnTu3yvsd7fvmm2/YsWOH9bNv3z6nup2qONpX0+8VkepT2BGRavHy8qKkpKRa965Zs4Zp06Zx00030adPH6Kjozl+/HjDNvACwcHBREVFsWnTJuu9kpIStm/ffsnPxsXF8eCDD/L555/z5JNP8s477wD2fwaO5zj07NkTb29vEhIS6Ny5s9NP+bomgA0bNli/p6WlcejQIbp3737J7xWRutE0lohUS/v27dm4cSPHjx8nICCAsLCwKu/t3Lkzn3/+OZMmTcIwDJ5//vmLjtA0lEceeYTZs2fTuXNnunfvzty5c0lLS7voPj0zZszguuuuo2vXrqSlpbF8+XJ69OgBQLt27TAMg6+//pqJEyfi6+tLYGAgM2fO5PHHH8dmszFy5EgyMzNZt24dAQEBTJ061Xr2iy++SHh4OFFRUTz33HNERERYK8Mu9r0iUjca2RGRapk5cybu7u707NmTVq1aXbT+Zs6cOYSGhjJ8+HAmTZrE+PHjueKKKxqxtXbPPPMMd9xxB/feey/Dhg0jICCA8ePH4+PjU+VnSkpKmD59Oj169GDChAl069aNv//97wC0bt2aF154gd/85jdERUXx8MMPA/CHP/yB3/3ud8yePZsePXowfvx4vvrqKzp06OD07FdeeYXHHnuMgQMHkpyczJdffuk0WlTV94pI3WgHZRG5bNhsNnr06MFtt93GH/7wh0b7Xu28LNK0NI0lIi7rxIkTLFmyhFGjRlFQUMC8efOIj4/nzjvvbOqmiUgj0jSWiLgsNzc33n//fQYPHsyIESPYvXs3y5YtUy2MyGVG01giIiLi0jSyIyIiIi5NYUdERERcmsKOiIiIuDSFHREREXFpCjsiIiLi0hR2RERExKUp7IiIiIhLU9gRERERl/b/AVpkr5ejfD2yAAAAAElFTkSuQmCC",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 准备优化器和模型\n",
    "vocab_size = len(dataset.token2id)\n",
    "hidden_size = 32\n",
    "n_layers = 1\n",
    "dropout = 0\n",
    "n_tags = len(dataset.label2id)\n",
    "batch_size = 128\n",
    "epochs = 20\n",
    "learning_rate = 1e-2\n",
    "\n",
    "lstm_crf = LSTM_CRF(vocab_size, hidden_size, n_layers, dropout, n_tags)\n",
    "train(lstm_crf, batch_size, epochs, learning_rate)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "8a9138a5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "precision = 0.6154769046190762, recall = 0.4892703862660944, f1 = 0.5451647183846972\n",
      "precision = 0.5415309446254072, recall = 0.40722596448254744, f1 = 0.46487242222998953\n"
     ]
    }
   ],
   "source": [
    "evaluate(train_X, train_Y, lstm_crf, batch_size)\n",
    "evaluate(test_X, test_Y, lstm_crf, batch_size)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3a763fe0-87e2-47b0-b8e2-3c781cf79f0c",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
